package com.kkwrite.regimen.provider.service.income;

import com.kkwrite.regimen.common.constant.DeletedEnum;
import com.kkwrite.regimen.common.constant.IncomeEnum;
import com.kkwrite.regimen.common.constant.SortTypeEnum;
import com.kkwrite.regimen.common.entity.dto.income.IncomeCountOutDTO;
import com.kkwrite.regimen.common.entity.dto.income.PageableIncomeDTO;
import com.kkwrite.regimen.common.entity.dto.item.ItemDetailDTO;
import com.kkwrite.regimen.common.entity.dto.user.UserOutDTO;
import com.kkwrite.regimen.provider.domain.RegIncomeDO;
import com.kkwrite.regimen.provider.domain.RegOrderDO;
import com.kkwrite.regimen.provider.domain.RegShopIncomeDO;
import com.kkwrite.regimen.provider.exception.DBNullException;
import com.kkwrite.regimen.provider.exception.ExtractCashException;
import com.kkwrite.regimen.provider.exception.ServiceException;
import com.kkwrite.regimen.provider.repository.RegIncomeRepository;
import com.kkwrite.regimen.provider.repository.RegOrderRepository;
import com.kkwrite.regimen.provider.repository.RegShopIncomeRepository;
import com.kkwrite.regimen.provider.service.item.ItemService;
import com.kkwrite.regimen.provider.service.order.OrderService;
import com.kkwrite.regimen.provider.service.user.UserService;
import com.kkwrite.regimen.provider.util.EntityManagerUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.Tuple;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author jinying
 */
@Service("incomeService")
public class IncomeServiceImpl implements IncomeService {
    private static final double INVITED_PEOPLE_ORDER_INCOME_RATIO = 0.05;
    private static final double SHOP_INVITED_PEOPLE_ORDER_INCOME_RATIO = 0.1;
    private static final double MIN_INCOME_AMOUNT = 0.01;
    private static final double FEE_RATIO = 0.01;
    private static final double MIN_EXTRACT_CASH_AMOUNT = 1;
    private static final int INCOME_FLAG_SHOP = 2;

    @Resource
    private EntityManagerUtil<RegIncomeDO> entityManagerUtil;

    @Resource(name = "regIncomeRepository")
    private RegIncomeRepository regIncomeRepository;
    @Resource(name = "regShopIncomeRepository")
    private RegShopIncomeRepository regShopIncomeRepository;

    @Resource(name = "regUserService")
    private UserService userService;
    @Resource(name = "itemService")
    private ItemService itemService;
    @Resource(name = "orderService")
    private OrderService orderService;
    @Resource(name = "regOrderRepository")
    private RegOrderRepository regOrderRepository;

    @Override
    public PageableIncomeDTO queryIncome(Integer page, Integer size,
                                         Long userId, Integer sortType) throws NumberFormatException,
            ServiceException {
        Sort.Direction direction = Sort.Direction.DESC;
        if (SortTypeEnum.POSITIVE_SEQUENCE.getValue().equals(sortType)) {
            direction = Sort.Direction.ASC;
        }
        Pageable pageable = new PageRequest(page, size, new Sort(direction,
                "gmtCreate"));
        RegIncomeDO regIncomeDo = new RegIncomeDO();
        regIncomeDo.setUserId(userId);
        Page<RegIncomeDO> pageData = regIncomeRepository.findAll(
                Example.of(regIncomeDo), pageable);
        PageableIncomeDTO pageableIncomeDto = new PageableIncomeDTO();
        pageableIncomeDto.setTotalPages(pageData.getTotalPages());
        pageableIncomeDto.setTotalElements(pageData.getTotalElements());
        List<PageableIncomeDTO.IncomeDTO> incomeDTOList = new ArrayList<>();
        pageData.getContent();
        pageData.getContent().forEach(
                (recordDo) -> {
                    PageableIncomeDTO.IncomeDTO incomeDto = pageableIncomeDto.creatIncomeDto();
                    BeanUtils.copyProperties(recordDo, incomeDto);
                    // 查用户
                    Optional<RegOrderDO> optional = Optional.ofNullable(regOrderRepository.findByOrderCode(incomeDto.getOrderCode()));
                    try {
                        RegOrderDO orderDO = optional.orElseThrow(DBNullException::new);
                        UserOutDTO userDTO = userService.queryUser(orderDO.getUserId() + "").getData();
                        if (userDTO != null) {
                            BeanUtils.copyProperties(userDTO, incomeDto);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    // 查询订单
                    if (incomeDto.getOrderCode() != null) {
                        orderService.findByOrderCode(incomeDto.getOrderCode()).ifPresent((regOrderDo) -> {
                            incomeDto.setOrderAmount(regOrderDo.getOrderAmount());
// 查项目
                            ItemDetailDTO itemDetailDTO = itemService.getItemDetail(regOrderDo.getItemId());
                            if (itemDetailDTO != null) {
                                incomeDto.setItemName(itemDetailDTO.getItemName());
                            }
                        });
                    }
                    incomeDto.setCreateTime(recordDo.getGmtCreate().toLocalDateTime());
                    incomeDTOList.add(incomeDto);
                });
        pageableIncomeDto.setRecordList(incomeDTOList);
        System.out.println("收益明细List:" + pageableIncomeDto.toString());
        return pageableIncomeDto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public IncomeCountOutDTO count(Long userId) {
        IncomeCountOutDTO out = new IncomeCountOutDTO();
        out.setInTotal(getUserIncomeInTotal(userId));
        out.setOutTotal(getUserIncomeOutTotal(userId));
        out.setTotal((out.getInTotal() * 100 + out.getOutTotal() * 100) / 100.0);
        out.setExtractCashPendingProcess(getUserIncomeExtractPending(userId));
        return out;
    }

    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public Double getUserIncomeExtractPending(Long userId) {
        Tuple tuple = entityManagerUtil.singleQuery(
                RegIncomeDO.class,
                (cb, root) -> cb.tuple(cb.sum(root.get("incomeAmount").as(Double.class)).alias("total")),
                (cb, root) -> cb.and(cb.equal(root.get("userId").as(Long.class), userId),
                        cb.equal(root.get("incomeStatus").as(Short.class), IncomeEnum.StatusEnum.PENDING_PROCESS.getStatus()),
                        cb.equal(root.get("incomeSourceType").as(Short.class), IncomeEnum.SourceTypeEnum.EXTRACT_CASH.getType())));
        Double total = tuple.get("total", Double.class);
        if (total == null) {
            return 0.0;
        }
        return total;
    }

    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public Double getUserIncomeOutTotal(Long userId) {
        Tuple tuple = entityManagerUtil.singleQuery(
                RegIncomeDO.class,
                (cb, root) -> cb.tuple(cb.sum(root.get("incomeAmount").as(Double.class)).alias("total")),
                (cb, root) -> cb.and(cb.equal(root.get("userId").as(Long.class), userId),
                        cb.notEqual(root.get("incomeStatus").as(Short.class), IncomeEnum.StatusEnum.CANCEL.getStatus()),
                        cb.lt(root.get("incomeAmount"), 0)));
        Double total = tuple.get("total", Double.class);
        if (total == null) {
            return 0.0;
        }
        return total;
    }

    @Transactional(rollbackFor = Exception.class, readOnly = true)
    public Double getUserIncomeInTotal(Long userId) {
        Tuple tuple = entityManagerUtil.singleQuery(
                RegIncomeDO.class,
                (cb, root) -> cb.tuple(cb.sum(root.get("incomeAmount").as(Double.class)).alias("total")),
                (cb, root) -> cb.and(cb.equal(root.get("userId").as(Long.class), userId),
                        cb.notEqual(root.get("incomeStatus").as(Short.class), IncomeEnum.StatusEnum.CANCEL.getStatus()),
                        cb.gt(root.get("incomeAmount"), 0)));
        Double total = tuple.get("total", Double.class);
        if (total == null) {
            return 0.0;
        }
        return total;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void extractCash(Double amount, Long userId) throws ExtractCashException, DBNullException {
        if (amount < MIN_EXTRACT_CASH_AMOUNT) {
            throw new ExtractCashException("小于最低提现金额");
        }
        Double totalCanUse = Optional.ofNullable(findCanExtractCash(userId)).orElseThrow(DBNullException::new);
        if (totalCanUse < amount) {
            throw new ExtractCashException("可用余额不足");
        }
        RegIncomeDO incomeDO = new RegIncomeDO();
        incomeDO.setUserId(userId);
        incomeDO.setIncomeSourceType(IncomeEnum.SourceTypeEnum.EXTRACT_CASH.getType());
        incomeDO.setIncomeStatus(IncomeEnum.StatusEnum.PENDING_PROCESS.getStatus());
        incomeDO.setIncomeAmount(-amount);
        incomeDO.setDeleted(DeletedEnum.UN_DELETED.getValue());
        // 手续费
        double fee = amount * FEE_RATIO;
        double actualAmount = (amount * 100 - fee * 100) / 100.0;
        incomeDO.setHandlingFee(fee);
        incomeDO.setActualAmount(actualAmount);
        incomeDO = regIncomeRepository.saveAndFlush(incomeDO);
        if (incomeDO.getIncomeId() == null) {
            throw new ServiceException("提现服务异常！");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void incomeRecordMessageHandler(int userId, String orderCode, double orderAmount, int shopId) {
        Optional.ofNullable(userService.findById(userId)).ifPresent((user) -> {
            Long inviteId = user.getInviterId();
            if (inviteId != null) {
                int flag = user.getInviteFlag();
                if (flag == 1) {
                    double incomeAmount = orderAmount * INVITED_PEOPLE_ORDER_INCOME_RATIO;
                    if (incomeAmount < MIN_INCOME_AMOUNT) {
                        return;
                    }
                    addUserIncomeRecord(inviteId.intValue(), orderCode,
                            IncomeEnum.SourceTypeEnum.INVITE_USER_ORDER.getType().intValue(), incomeAmount, null);
                } else if (flag == INCOME_FLAG_SHOP && inviteId != shopId) {
                    double incomeAmount = orderAmount * SHOP_INVITED_PEOPLE_ORDER_INCOME_RATIO;
                    if (incomeAmount < MIN_INCOME_AMOUNT) {
                        return;
                    }
                    addShopIncomeRecord(inviteId.intValue(), orderCode, 2, incomeAmount, null);
                }
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    public void addUserIncomeRecord(int userId, String orderCode, int sourceType, double amount, Integer status) {
        if (amount == 0) {
            return;
        }
        RegIncomeDO incomeDO = new RegIncomeDO();
        incomeDO.setUserId((long) userId);
        incomeDO.setOrderCode(orderCode);
        incomeDO.setIncomeSourceType((short) sourceType);
        incomeDO.setIncomeAmount(amount);
        if (status == null) {
            incomeDO.setIncomeStatus(IncomeEnum.StatusEnum.PENDING_PROCESS.getStatus());
        } else {
            incomeDO.setIncomeStatus(status.shortValue());
        }
        incomeDO.setDeleted(DeletedEnum.UN_DELETED.getValue());
        regIncomeRepository.save(incomeDO);
    }

    @Transactional(rollbackFor = Exception.class)
    public void addShopIncomeRecord(int shopId, String orderCode, int sourceType, double amount, Integer status) {
        if (amount == 0) {
            return;
        }
        RegShopIncomeDO incomeDO = new RegShopIncomeDO();
        incomeDO.setShopId((long) shopId);
        incomeDO.setOrderCode(orderCode);
        incomeDO.setIncomeSourceType((short) sourceType);
        incomeDO.setIncomeAmount(amount);
        if (status == null) {
            incomeDO.setIncomeStatus((short) 1);
        } else {
            incomeDO.setIncomeStatus(status.shortValue());
        }
        incomeDO.setDeleted(DeletedEnum.UN_DELETED.getValue());
        regShopIncomeRepository.save(incomeDO);
    }

    @Transactional(rollbackFor = Exception.class)
    public Double findCanExtractCash(Long userId) {
        Tuple tuple = entityManagerUtil.singleQuery(
                RegIncomeDO.class,
                (cb, root) -> cb.tuple(cb.sum(root.get("incomeAmount").as(Double.class)).alias("total")),
                (cb, root) -> {
                    Predicate p1 = cb.equal(root.get("userId"), userId);
                    Predicate p2 = cb.equal(root.get("incomeStatus"), IncomeEnum.StatusEnum.NORMAL.getStatus());
                    Predicate p3 = cb.equal(root.get("incomeStatus"), IncomeEnum.StatusEnum.PENDING_PROCESS.getStatus());
                    Predicate p4 = cb.lt(root.get("incomeAmount"), 0);
                    return cb.and(p1, cb.or(p2, cb.and(p3, p4)));
                });
        Double total = tuple.get("total", Double.class);
        if (total == null) {
            return 0.0;
        }
        return total;
    }

}
